//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace TridentProvenanceTray
{
    using System;
    using System.ComponentModel;
    using System.Configuration;
    using System.Drawing;
    using System.Windows;
    using System.Windows.Forms;
    using Microsoft.Research.ProvenanceInterfaces;
    using Microsoft.Research.ProvenanceSubscriber;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using Microsoft.Practices.Unity;
    using Microsoft.VisualBasic.ApplicationServices;
    using System.Globalization;

    /// <summary>
    /// Hosts the Executor in the Windows tray and monitors for new job to be run.
    /// </summary>
    public partial class MainWindow : Window, IDisposable
    {
        #region Member Data

        /// <summary>
        /// Holds the default font name
        /// </summary>
        private const string DEFAULT_FONT = "Segoe UI";

        /// <summary>
        /// Balloon tool tip timeout
        /// </summary>
        private const int BALLOON_TOOLTIP_TIMEOUT = 100;

        /// <summary>
        /// Unity container.
        /// </summary>
        private UnityContainer container;

        /// <summary>
        /// Provenance subscriber instance.
        /// </summary>
        private IProvenanceSubscriber provenanceSubScriber;

        /// <summary>
        /// Specifies whether the provenance subscriber is started or not.
        /// </summary>
        private bool IsProvenanceSubscriberStarted;

        /// <summary>
        /// Manual reset event object used to synchronize communication between current thread and background timer.
        /// </summary>
        private System.Threading.ManualResetEvent manualResetEvent;

        /// <summary>
        /// Notify icon which is available for the user to interact in the Windows Task bar tray and to notify if there is any new job started executing
        /// </summary>
        private NotifyIcon ProvenanceNotifyIcon;

        /// <summary>
        /// ContextMenu for the tray icon.
        /// </summary>
        private ContextMenuStrip contextMenuStrip;

        /// <summary>
        /// ContextMenu item to get the about window.
        /// </summary>
        private ToolStripMenuItem menuItemShowAbout;

        /// <summary>
        /// ContextMenu item to close the tray application.
        /// </summary>
        private ToolStripMenuItem menuItemExitProvenance;

        /// <summary>
        /// About window
        /// </summary>
        private AboutBox aboutWindow;

        #endregion

        #region Public Methods

        /// <summary>
        /// Initializes a new instance of the <see cref="MainWindow"/> class.
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            this.ProvenanceNotifyIcon = new NotifyIcon();
            this.ProvenanceNotifyIcon.BalloonTipText = Properties.Resources.ProvenancetrayBallonText;
            this.ProvenanceNotifyIcon.ContextMenuStrip = this.contextMenuStrip;
            this.ProvenanceNotifyIcon.Icon = Properties.Resources.provenanceIcon;
            this.ProvenanceNotifyIcon.Text = Properties.Resources.ProvenanceTrayAppToolTip;
            this.ProvenanceNotifyIcon.Visible = true;
            this.ProvenanceNotifyIcon.DoubleClick += new EventHandler(ShowAboutWindow);

            this.contextMenuStrip = new ContextMenuStrip();
            this.contextMenuStrip.Opening += new System.ComponentModel.CancelEventHandler(this.contextMenuStrip1_Opening);

            this.ProvenanceNotifyIcon.ContextMenuStrip = this.contextMenuStrip;

            this.menuItemExitProvenance = new ToolStripMenuItem(Properties.Resources.ExitProvenanceLabel);
            this.menuItemExitProvenance.Click += new EventHandler(OnMenuItemExitClick);

            this.menuItemShowAbout = new ToolStripMenuItem(Properties.Resources.AboutProvenanceLabel);
            this.menuItemShowAbout.Font = new Font(DEFAULT_FONT, 9F, System.Drawing.FontStyle.Bold);
            this.menuItemShowAbout.Click += new EventHandler(ShowAboutWindow);

            Hide();

            InitializeService();

            LoadProvenanceSubscriber();
        }

        /// <summary>
        /// Entry point for the Executor
        /// </summary>
        [STAThread]
        public static void Main(string[] args)
        {
            // Create the single instance manager.
            SingleInstanceManager manager = new SingleInstanceManager();
            manager.Run(args);            
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Handles the Loaded event of the Window control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            this.Hide();
        }

        /// <summary>
        /// Called when [menu item exit click].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        void OnMenuItemExitClick(object sender, EventArgs e)
        {
            this.ShutdownService();
        }

        /// <summary>
        /// Shows the about window
        /// </summary>
        private void ShowAboutWindow(object sender, EventArgs e)
        {
            if (IsProvenanceSubscriberStarted)
            {
                ////If the AboutWindow is not initialized or AboutWindow is initialized but closed/invisible
                if ((this.aboutWindow == null) || (this.aboutWindow != null && this.aboutWindow.IsVisible == false))
                {
                    this.aboutWindow = new AboutBox();
                }
                ////If it is not visible bring it into view
                if (this.aboutWindow.IsVisible == false)
                {
                    this.aboutWindow.ShowDialog();
                }
                ////Already AboutWindow is visble but user is selecting again, just activate the window
                else
                {
                    this.aboutWindow.Activate();
                }
            }
        }

        /// <summary>
        /// Notifies the specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        private void Notify(string message)
        {
            if (this.ProvenanceNotifyIcon != null)
            {
                this.ProvenanceNotifyIcon.ShowBalloonTip(
                    BALLOON_TOOLTIP_TIMEOUT,
                    Properties.Resources.ProvenancetrayBallonTitle,
                    message,
                    ToolTipIcon.Info);
            }
        }

        /// <summary>
        /// Initializes provenance subscriber service.
        /// </summary>
        private void InitializeService()
        {
            Notify(Properties.Resources.InitializingProvenanceService);

            this.container = new UnityContainer();
            try
            {
                this.provenanceSubScriber = this.container.Resolve<ProvenancePublisher>();
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleUIException(ex);
                TridentMessageBox.ShowTridentErrorMessageBox(ex.Message);
                Environment.Exit(0);
            }

            Notify(Properties.Resources.InitializedProvenanceService);
        }

        /// <summary>
        /// Start the provenance subscriber service.
        /// </summary>
        private void LoadProvenanceSubscriber()
        {
            int failureAttempts = 0;
            int configuredAttempts = 0;

            int timeInterval = 0;

            string retryInterval = ConfigurationManager.AppSettings["RetryInterval"];
            if (!string.IsNullOrEmpty(retryInterval))
            {
                if (!int.TryParse(retryInterval, out timeInterval))
                {
                    // By Default the timeinterval is set to 5 seconds.
                    timeInterval = 5000;
                }
            }

            string attempts = ConfigurationManager.AppSettings["RetryCount"];
            if (!string.IsNullOrEmpty(attempts))
            {
                if (!int.TryParse(attempts, out configuredAttempts))
                {
                    // By default the retry interval is set to 36.
                    configuredAttempts = 36;
                }
            }

            System.Timers.Timer timer = new System.Timers.Timer(timeInterval);
            timer.Elapsed += new System.Timers.ElapsedEventHandler(this.OnDelayTimerElapsed);

            Notify(Properties.Resources.StartingProvenanceService);
            do
            {
                try
                {
                    this.provenanceSubScriber.Start();

                    this.IsProvenanceSubscriberStarted = true;
                }
                catch (Exception ex)
                {
                    this.IsProvenanceSubscriberStarted = false;

                    if (configuredAttempts != 0 && failureAttempts > configuredAttempts)
                    {
                        TridentErrorHandler.HandleUIException(ex);
                        TridentMessageBox.ShowTridentErrorMessageBox(ex.Message);
                        if (this.ProvenanceNotifyIcon != null)
                        {
                            ProvenanceNotifyIcon.Visible = false;
                        }

                        Environment.Exit(0);
                    }

                    failureAttempts++;

                    timer.Start();

                    // Create the manual reset event and wait on it for the timer to elaspe.
                    this.manualResetEvent = new System.Threading.ManualResetEvent(false);
                    this.manualResetEvent.WaitOne();
                }
            } while (!this.IsProvenanceSubscriberStarted);

            Notify(Properties.Resources.StartedProvenanceService);
        }

        /// <summary>
        /// Fired when the delay timeout duration elapses.
        /// </summary>
        /// <param name="sender">Background timer.</param>
        /// <param name="e">Attributes of the event.</param>
        private void OnDelayTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            System.Timers.Timer timer = sender as System.Timers.Timer;
            if (timer != null)
            {
                // Stop the timer.
                timer.Stop();
            }

            // Signal the event so that the thread which is waiting on it can resume.
            this.manualResetEvent.Set();
        }

        /// <summary>
        /// Handles the Opening event of the contextMenuStrip control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.CancelEventArgs"/> instance containing the event data.</param>
        private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
        {
            if (IsProvenanceSubscriberStarted)
            {
                if (this.contextMenuStrip.Items != null && this.contextMenuStrip.Items.Count > 0)
                {
                    this.contextMenuStrip.Items.Clear();
                }

                this.contextMenuStrip.Items.Add(this.menuItemShowAbout);
                this.contextMenuStrip.Items.Add(this.menuItemExitProvenance);
                this.contextMenuStrip.Refresh();
            }
        }

        /// <summary>
        /// Shutdowns the application.
        /// </summary>
        private void ShutdownService()
        {
            try
            {
                this.provenanceSubScriber.Halt();
                this.ProvenanceNotifyIcon.Visible = false;
                Environment.Exit(0);
            }
            catch (TridentCustomException ex)
            {
                if (ex.ErrorNumber.Equals(TridentErrorConstants.ErrorNumber1100000016.ToString(CultureInfo.InvariantCulture)))
                {
                    TridentMessageBox.ShowTridentWarningMessageBox(ex.Message);
                }
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleUIException(ex);
            }

        }
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.ProvenanceNotifyIcon != null)
                    this.ProvenanceNotifyIcon.Dispose();
                if (this.contextMenuStrip != null)
                    this.contextMenuStrip.Dispose();
                if (this.menuItemExitProvenance != null)
                    this.menuItemExitProvenance.Dispose();
                if (this.menuItemShowAbout != null)
                    this.menuItemShowAbout.Dispose();
                if (this.container != null)
                    this.container.Dispose();
            }
        }

        #endregion
    }

    # region SingleInstanceManager

    // Using VB	bits to	detect single instances	and	process	accordingly:
    //	* OnStartup	is fired when the first	instance loads
    //	* OnStartupNextInstance	is fired when the application is re-run	again
    //	  NOTE:	it is redirected to	this instance thanks to	IsSingleInstance
    public class SingleInstanceManager : WindowsFormsApplicationBase
    {
        System.Windows.Application app;

        /// <summary>
        /// Initializes a new instance of the <see cref="SingleInstanceManager"/> class.
        /// </summary>
        public SingleInstanceManager()
        {
            this.IsSingleInstance = true;
        }

        /// <summary>
        /// Raises the <see cref="E:Startup"/> event.
        /// </summary>
        /// <param name="e">The <see cref="Microsoft.VisualBasic.ApplicationServices.StartupEventArgs"/> instance containing the event data.</param>
        /// <returns></returns>
        protected override bool OnStartup(Microsoft.VisualBasic.ApplicationServices.StartupEventArgs eventArgs)
        {
            // First time app is launched
            app = new System.Windows.Application();
            app.Run(new MainWindow());
            return false;
        }

        /// <summary>
        /// When overridden in a derived class, allows for code to run when a subsequent instance of a single-instance application starts.
        /// </summary>
        /// <param name="eventArgs"><see cref="T:Microsoft.VisualBasic.ApplicationServices.StartupNextInstanceEventArgs"/>. Contains the command-line arguments of the subsequent application instance and indicates whether the first application instance should be brought to the foreground upon exiting the exception handler.</param>
        protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
        {
            // Subsequent launches
            base.OnStartupNextInstance(eventArgs);
            app.MainWindow.Activate();
        }
    }

    #endregion
}
